/* SexpClientFormatterSink.cs -- 
   Copyright (C) 2008  Casey Marshall.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.  */


using System;
using System.Collections;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;

using SexpSharp.Serialization;

namespace SexpSharp.Remoting
{
	public sealed class SexpClientFormatterSink : IChannelSinkBase, IClientChannelSink,
		IClientFormatterSink, IMessageSink
	{
		private IClientChannelSink channel;
		private SexpFormatter serialFormatter;
		private SexpFormatter deserialFormatter;

		public SexpClientFormatterSink(IClientChannelSink channel,
                                       SexpFormatter serialFormatter,
                                       SexpFormatter deserialFormatter)
		{
			this.channel = channel;
			this.serialFormatter = serialFormatter;
			this.deserialFormatter = deserialFormatter;
		}

#region IChannelSinkBase implementation

        public IDictionary Properties
		{
			get {
				throw new NotImplementedException();
			}
        }

#endregion

#region IClientChannelSink implementation

        public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
										ITransportHeaders headers, Stream stream)
		{
			throw new NotImplementedException();
		}

        public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack,
										 object state, ITransportHeaders headers,
										 Stream stream)
		{
			IMethodCallMessage callMsg = (IMethodCallMessage) state;
			IMessage response = DeserializeMessage(stream, headers, callMsg);
			sinkStack.DispatchReplyMessage(response);
		}

        public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
		{
			throw new NotImplementedException();
		}

        public void ProcessMessage(IMessage msg, ITransportHeaders requestHeaders,
								   Stream requestStream, out ITransportHeaders responseHeaders,
								   out Stream responseStream)
		{
			throw new NotImplementedException();
		}

        public IClientChannelSink NextChannelSink
		{
			get {
				return channel;
			}
        }

#endregion

#region IMessageSink implementation

		public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
		{
			Stream requestStream;
			ITransportHeaders requestHeaders;
			SerializeMessage(msg, out requestStream, out requestHeaders);
			ClientChannelSinkStack stack = new ClientChannelSinkStack(replySink);
			stack.Push(this, msg);
			channel.AsyncProcessRequest(stack, msg, requestHeaders, requestStream);
			return null;
		}

		public IMessage SyncProcessMessage(IMessage msg)
		{
			Stream requestStream, responseStream;
			ITransportHeaders requestHeaders, responseHeaders;
			
			SerializeMessage(msg, out requestStream, out requestHeaders);
			channel.ProcessMessage(msg, requestHeaders, requestStream,
								   out responseHeaders, out responseStream);
			return DeserializeMessage(responseStream, responseHeaders,
									  (IMethodCallMessage) msg);
		}

		public IMessageSink NextSink
		{
			get {
				return null;
			}
        }

#endregion

		private void SerializeMessage(IMessage msg, out Stream requestStream,
									  out ITransportHeaders requestHeaders)
		{
			IMethodCallMessage callmsg = (IMethodCallMessage) msg;
			SexpCall call = new SexpCall();
			call.methodName = callmsg.MethodName;
			object[] attr = callmsg.MethodBase.GetCustomAttributes(typeof(SexpMethodName),
																   true);
			if (attr.Length > 0)
				call.methodName = ((SexpMethodName) attr[0]).MethodName;
			// Strip away assembly args from the type.
			call.typeName = callmsg.TypeName.Split(',')[0];
			call.args = (object[]) callmsg.Args.Clone();
			requestHeaders = new TransportHeaders();

			requestStream = channel.GetRequestStream(msg, requestHeaders);
			if (requestStream == null)
				requestStream = new MemoryStream();

			serialFormatter.Serialize(requestStream, call, null);

			if (requestStream is MemoryStream)
				requestStream.Position = 0;
		}

		private IMessage DeserializeMessage(Stream responseStream,
											ITransportHeaders responseHeaders,
											IMethodCallMessage callMsg)
		{
			object obj = deserialFormatter.Deserialize(responseStream, null);
			if (obj is SexpFault)
				return new SexpMethodResponseMessage((SexpFault) obj, callMsg);
			if (obj is SexpResponse)
				return new SexpMethodResponseMessage((SexpResponse) obj, callMsg);
			throw new RemotingException("message of wrong type: " + obj.GetType());
		}
	}
}